/**
 * Created by ${PRODUCT_NAME}.
 * User: skylerparr
 * Date: 12/27/10
 * Time: 8:33 AM
 * To change this template use File | Settings | File Templates.
 */
package com.thoughtorigin.data {
import com.thoughtorigin.application.ApplicationSettings;
import com.thoughtorigin.application.FeedItem;
import com.thoughtorigin.loader.AbstractLoader;
import com.thoughtorigin.test.AsyncDispatcher;

import flash.events.Event;
import flash.net.URLRequestMethod;

import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertTrue;
import org.flexunit.asserts.fail;
import org.flexunit.async.Async;
import org.mockito.integrations.any;
import org.mockito.integrations.atLeast;
import org.mockito.integrations.between;
import org.mockito.integrations.callOriginal;
import org.mockito.integrations.given;
import org.mockito.integrations.havingPropertyOf;
import org.mockito.integrations.mock;
import org.mockito.integrations.never;
import org.mockito.integrations.useArgument;
import org.mockito.integrations.verify;

[RunWith("org.mockito.integrations.flexunit4.MockitoClassRunner")]
[Mock(type="com.thoughtorigin.loader.AbstractLoader")]
[Mock(type="com.thoughtorigin.data.ValueObject")]
[Mock(type="com.thoughtorigin.application.FeedItem")]
[Mock(type="com.thoughtorigin.data.DataCache")]
[Mock(type="com.thoughtorigin.application.ApplicationSettings")]
public class CachedDataLoaderTest {

    public var _dataLoader: TestableDataLoader;
    private var _applicationSettings: ApplicationSettings;

    [Before]
    public function setup(): void {
        _applicationSettings = mock(ApplicationSettings);
        _dataLoader = new TestableDataLoader();
        _dataLoader.applicationSettings = _applicationSettings;
    }

    [After]
    public function tearDown(): void {
        _dataLoader = null;
        _applicationSettings = null;
    }

    [Test(async)]
    public function shouldLoadDataFromValueObject(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var fi: FeedItem = mock(FeedItem);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(vo.feedName);
            verify(between(1,1)).that(fi.url);
            verify(between(1,1)).that(loader.start());

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        given(_applicationSettings.getFeedById(any())).willReturn(fi);
        given(fi.url).willReturn("url");
        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        _dataLoader.loadValueObject(MockValue, callback);
    }

    [Test(async)]
    public function shouldLoadDataFromUrl(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(loader.start());

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        var feedItem: FeedItem = mock(FeedItem);
        given(feedItem.url).willReturn(any());

        _applicationSettings = mock(ApplicationSettings);
        given(_applicationSettings.getFeedById(vo.feedName)).willReturn(feedItem);
        given(_applicationSettings.baseFeedUrl).willReturn("");
        _dataLoader.applicationSettings = _applicationSettings;

        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
    }

    [Test]
    public function shouldLoadFromCache(): void {
        var callbackCalled: Boolean = false;
        var cache: DataCache = mock(DataCache);
        var vo: MockValue = new MockValue();
        var fi: FeedItem = mock(FeedItem);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(cache.isAssetCached(any()));
            verify(between(1,1)).that(cache.getAsset(any()));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            callbackCalled = true;
        }

        given(_applicationSettings.getFeedById(any())).willReturn(fi);
        given(fi.url).willReturn("url");
        given(cache.isAssetCached(any())).willReturn(true);
        given(cache.getAsset(any())).willReturn(vo);

        _dataLoader.dataCache = cache;
        _dataLoader._vo = vo;
        _dataLoader.loadValueObject(MockValue, callback);

        if(!callbackCalled) {
            fail();
        }
    }

    [Test]
    public function shouldLoadUrlFromCache(): void {
        var callbackCalled: Boolean = false;
        var cache: DataCache = mock(DataCache);
        var vo: MockValue = new MockValue();

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(cache.isAssetCached(any()));
            verify(between(1,1)).that(cache.getAsset(any()));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            callbackCalled = true;
        }

        given(cache.isAssetCached(any())).willReturn(true);
        given(cache.getAsset(any())).willReturn(vo);

        _dataLoader.dataCache = cache;
        _dataLoader._vo = vo;
        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);

        if(!callbackCalled) {
            fail();
        }
    }

    [Test(async)]
    public function shouldCacheValueObject(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var fi: FeedItem = mock(FeedItem);
        var cache: DataCache = mock(DataCache);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(vo.feedName);
            verify(between(1,1)).that(fi.url);
            verify(between(1,1)).that(loader.start());
            verify(between(1,1)).that(cache.cacheAsset(loader));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        given(_applicationSettings.getFeedById(any())).willReturn(fi);
        given(fi.url).willReturn("url");
        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;
        _dataLoader.dataCache = cache;
        given(cache.isAssetCached(any())).willReturn(false);

        _dataLoader.loadValueObject(MockValue, callback);
    }

    [Test(async)]
    public function shouldNotCacheValueObjectIfExplicitlyToldNotTo(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var fi: FeedItem = mock(FeedItem);
        var cache: DataCache = mock(DataCache);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(vo.feedName);
            verify(between(1,1)).that(fi.url);
            verify(between(1,1)).that(loader.start());
            verify(never()).that(cache.cacheAsset(loader));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        given(_applicationSettings.getFeedById(any())).willReturn(fi);
        given(fi.url).willReturn("url");
        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;
        _dataLoader.dataCache = cache;
        given(cache.isAssetCached(any())).willReturn(false);

        _dataLoader.loadValueObject(MockValue, callback, true);
    }

    [Test(async)]
    public function shouldCacheDataFromUrl(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var cache: DataCache = mock(DataCache);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(loader.start());
            verify(between(1,1)).that(cache.cacheAsset(loader));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        _dataLoader.dataCache = cache;
        given(cache.isAssetCached(any())).willReturn(false);

        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        var feedItem: FeedItem = mock(FeedItem);
        given(feedItem.url).willReturn(any());

        _applicationSettings = mock(ApplicationSettings);
        given(_applicationSettings.getFeedById(vo.feedName)).willReturn(feedItem);
        given(_applicationSettings.baseFeedUrl).willReturn("");
        _dataLoader.applicationSettings = _applicationSettings;

        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
    }


    [Test(async)]
    public function shouldNotCacheDataFromUrlIfNoCacheSetToTrue(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var cache: DataCache = mock(DataCache);

        var callback: Function = function(loaded: Object): void {
            verify(between(0,1)).that(loader.start());
            verify(between(0,1)).that(cache.cacheAsset(loader));
            verify(between(0,1)).that(_applicationSettings.getFeedById(any()));
            verify(between(0,1)).that(_applicationSettings.baseFeedUrl);
            verify(between(0,1)).that(feedItem.url);

            verify(never()).that(cache.cacheAsset(loader));

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        _dataLoader.dataCache = cache;
        given(cache.isAssetCached(any())).willReturn(false);

        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        var feedItem: FeedItem = mock(FeedItem);
        given(feedItem.url).willReturn(any());

        _applicationSettings = mock(ApplicationSettings);
        given(_applicationSettings.getFeedById(vo.feedName)).willReturn(feedItem);
        given(_applicationSettings.baseFeedUrl).willReturn("");

        _dataLoader.applicationSettings = _applicationSettings;

        _dataLoader.loadObjectFromUrl(MockValue, "url", callback,true);
    }

    [Test(async)]
    public function shouldNotLoadMoreThanOnce(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);
        var cache: DataCache = mock(DataCache);

        var callback: Function = function(loaded: Object): void {
            verify(between(0,1)).that(loader.start());
            verify(between(0,1)).that(cache.cacheAsset(loader));
            verify(between(0,4)).that(_applicationSettings.getFeedById(any()));
            verify(between(0,4)).that(_applicationSettings.baseFeedUrl);
            verify(between(0,4)).that(feedItem.url);

            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        _dataLoader.dataCache = cache;
        given(cache.isAssetCached(any())).willReturn(false);

        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        var feedItem: FeedItem = mock(FeedItem);
        given(feedItem.url).willReturn(any());

        _applicationSettings = mock(ApplicationSettings);
        given(_applicationSettings.getFeedById(vo.feedName)).willReturn(feedItem);
        given(_applicationSettings.baseFeedUrl).willReturn(any());

        _dataLoader.applicationSettings = _applicationSettings;

        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
    }

    [Test(async)]
    public function shouldLoadDataUsingPostMethod(): void {
        var loader: AbstractLoader = mock(AbstractLoader, "", ["url"]);
        loader.requestMethod = "POST";
        _dataLoader._mockLoader = loader;
        var vo: MockValue = new MockValue();
        assertNotNull(vo);

        var callback: Function = function(loaded: Object): void {
            verify(between(1,1)).that(loader.start());

            verify(atLeast(1)).that(loader.requestMethod = "POST");
            assertNotNull(loaded);
            assertEquals(vo, loaded);

            var d: AsyncDispatcher = new AsyncDispatcher();
            d.addEventListener(Event.COMPLETE, async);
            d.doDispatch();
        }

        var async: Function = Async.asyncHandler(this, function(e: Event, o: Object): void {
            assertTrue(true);
        }, 200);

        given(loader.addEventListener(any(), any())).will(callOriginal());
        given(loader.dispatchEvent(any())).will(callOriginal());
        given(loader.start()).will(new EventAnswer(loader, new Event(Event.COMPLETE)));
        _dataLoader._vo = vo;

        var feedItem: FeedItem = mock(FeedItem);
        given(feedItem.url).willReturn("url");
        given(feedItem.method).willReturn(URLRequestMethod.POST);

        _applicationSettings = mock(ApplicationSettings);
        given(_applicationSettings.getFeedById(vo.feedName)).willReturn(feedItem);
        given(_applicationSettings.baseFeedUrl).willReturn("");
        _dataLoader.applicationSettings = _applicationSettings;

        _dataLoader.loadObjectFromUrl(MockValue, "url", callback);
    }
}
}

import com.thoughtorigin.data.CachedDataLoader;
import com.thoughtorigin.data.ValueObject;
import com.thoughtorigin.loader.AbstractLoader;

import flash.events.Event;
import flash.utils.setTimeout;

import org.mockito.api.Answer;

class TestableDataLoader extends CachedDataLoader {
    public var _mockLoader: AbstractLoader;
    public var _vo: Object;

    override protected function createLoader(url:String):AbstractLoader {
        return _mockLoader;
    }

    override protected function instantiate(clazz:Class, args:Array): Object {
        return _vo;
    }
}

class MockValue implements ValueObject {

    public function get feedName():String {
        return "sample";
    }

    public function populate(obj:Object, callback:Function):void {
        callback.call(this);
    }
}

class EventAnswer implements Answer {

    private var _loader: AbstractLoader;
    private var _eventToDispatch: Event;

    public function EventAnswer(loader: AbstractLoader, eventToDispatch: Event) {
        _loader = loader;
        _eventToDispatch = eventToDispatch;
    }

    public function give():* {
        setTimeout(function(): void {
            _loader.dispatchEvent(_eventToDispatch);
        }, 20);
    }
}

